/*
 * Board.cpp
 *
 *  Created on: Jun 26, 2009
 *      Author: mlrus
 */

#include <strings.h>
#include <sstream>
#include <iostream>
#include <ostream>
#include <iomanip>

#include "Common.h"
#include "Cell.h"
#include "Board.h"
#include "Util.h"

Board::SquareType const Board::standardTiles[Common::boardSize * Common::boardSize] = {

		TW, RG, RG, DL, RG, RG, RG, TW, RG, RG, RG, DL, RG, RG, TW,

		RG, DW, RG, RG, RG, TL, RG, TW, RG, TL, RG, RG, RG, DW, RG,

		RG, RG, DW, RG, RG, RG, DL, RG, DL, RG, RG, RG, DW, RG, RG,

		DL, RG, RG, DW, RG, RG, RG, DL, RG, RG, RG, DW, RG, RG, DL,

		RG, RG, RG, RG, DW, RG, RG, RG, RG, RG, DW, RG, RG, RG, RG,

		RG, TL, RG, RG, RG, TL, RG, RG, RG, TL, RG, RG, RG, TL, RG,

		RG, RG, DL, RG, RG, RG, DL, RG, DL, RG, RG, RG, DL, RG, RG,

		TW, RG, RG, DL, RG, RG, RG, DW, RG, RG, RG, DL, RG, RG, TW,

		RG, RG, DL, RG, RG, RG, DL, RG, DL, RG, RG, RG, DL, RG, RG,

		RG, TL, RG, RG, RG, TL, RG, RG, RG, TL, RG, RG, RG, TL, RG,

		RG, RG, RG, RG, DW, RG, RG, RG, RG, RG, DW, RG, RG, RG, RG,

		DL, RG, RG, DW, RG, RG, RG, DL, RG, RG, RG, DW, RG, RG, DL,

		RG, RG, DW, RG, RG, RG, DL, RG, DL, RG, RG, RG, DW, RG, RG,

		RG, DW, RG, RG, RG, TL, RG, TW, RG, TL, RG, RG, RG, DW, RG,

		TW, RG, RG, DL, RG, RG, RG, TW, RG, RG, RG, DL, RG, RG, TW

};

const Board::SquareType* const Board::standardTilesStart() {
	return standardTiles;
}

const Board::SquareType* const Board::standardTilesEnd() {
	return standardTiles + (Common::boardSize * Common::boardSize);
}

const int Board::numNeighbors = 4;
const Board::getNeighbor Board::neighborAccess[Board::numNeighbors] = { &Board::getLeft, &Board::getRight,
		&Board::getUp, &Board::getDown };
const Board::getNeighbor Board::neighborHorizontalAccess[2] = { &Board::getLeft, &Board::getRight };
const Board::getNeighbor Board::neighborVerticalAccess[2] = { &Board::getUp, &Board::getDown };

inline Cell* Board::getCell(uint row, uint col) {
	return squares + row * Common::boardSize + col;
}

inline const Axis::Axis Board::getAxis(const Cell* head, const Cell* tail) {
	if (head == tail)
		return Axis::POINT;
	int headIndx = head - squares;
	int tailIndx = tail - squares;
	if (headIndx / Common::boardSize == tailIndx / Common::boardSize)
		return Axis::HORIZONTAL;
	if ((tailIndx - headIndx) % Common::boardSize == 0)
		return Axis::VERTICAL;
	return Axis::INVALID;
}

bool Board::hasAdjacent(const Cell *cell, Axis::Axis axis) {
	if (cell >= squares && cell < squares + Common::boardSize * Common::boardSize) {
		Board::getNeighbor *nlist = axis == Axis::HORIZONTAL ? neighborHorizontalAccess : neighborVerticalAccess;
		const Cell *neighbor;
		for (int i = 0; i < 2; i++) {
			neighbor = (this->*nlist[i])(cell);
			if (neighbor != NULL && neighbor->isFilled())
				return true;
		}
	}
	return false;
}

bool Board::hasAdjacent(const Cell *cell) {
	if (cell >= squares && cell < squares + Common::boardSize * Common::boardSize) {
		const Cell *neighbor;
		for (int i = 0; i < numNeighbors; i++) {
			neighbor = (this->*neighborAccess[i])(cell);
			if (neighbor != NULL && neighbor->isFilled())
				return true;
		}
	}
	return false;
}

const string& Board::cleanString(string& src, const string& valid = Common::VALIDCHARS, uchar replacement =
	Common::EMPTYCHAR) {
	size_t pos = 0;
	while ((pos = src.find_first_not_of(valid, pos)) != string::npos) {
		src[pos] = replacement;
	}
	return src;
}

void Board::read(const string& filename) {
	try {
		ifstream infile(filename.c_str(), std::ios_base::in);
		string line;
		Cell *cellPtr = firstSquare();
		Cell *endptr = lastSquare();
		while (cellPtr < endptr && getline(infile, line, '\n')) {
			Util::trim(line);
			Util::upperCase(line);
			const char *cstr = line.c_str();
			Cell *eptr = cellPtr + Common::boardSize;
			while (cellPtr < eptr) {
				uchar c = *cstr;
				if (Common::VALIDCHARS.find(c) == string::npos)
					c = Common::EMPTYCHAR;
				setCell(cellPtr, c);
				if (c != Common::EMPTYCHAR)
					cementTile(*cellPtr);
				cellPtr++;
				if (*cstr != 0)
					cstr++;
			}
		}
		infile.close();
		cout << toString();
	} catch (...) {
		cerr << "Error reading " << filename << endl;
	}
	preservePriorsquares();
}

bool Board::changed(const Cell* cell) {
	ptrdiff_t dif = cell - squares;
	Cell *otherCell = priorSquares + dif;
	bool chg = (*cell != *otherCell);
	return chg;
}

uint Board::scoreMove(const Cell* cell, Axis::Axis axis) {
	int primaryScore = scoreAxis(cell, axis);
	int dualScores = 0;
	Cell *from, *to;
	uint incr = getIncrement(axis);
	Axis::Axis dual = getDual(axis);
	if (getEndpoints(cell, axis, from, to)) {
		for (const Cell* c = from; c <= to; c += incr) {
			if (changed(c)) {
				int sec = scoreAxis(c, dual, "   ");
				dualScores += sec;
			}
		}
	}
	uint totalScore = primaryScore + dualScores;
	return totalScore;
}

uint Board::scoreAxis(const Cell* cell, Axis::Axis axis, string prefix) {
	uint score = 0;
	uint wf = 1;
	uint incr = getIncrement(axis);
	Cell *from, *to;
	if (getEndpoints(cell, axis, from, to) && from != to) {
		//cout << "\n\n";
		for (const Cell* c = from; c <= to; c += incr) {
			uint lf = c->letterFactor;
			uint lwf = c->wordFactor;
			uint va = c->value;
			//cout << prefix << c->letter << " [value="<<va<<"*"<<lf<<", wf="<<wf<<"]       "<<endl;
			wf *= lwf;
			score += lf * va;
		}
		score *= wf;
		//cout << prefix << "TOTAL  " << score;
	}
	return score;
}

Cell *Board::firstSquare() {
	return squares;
}

Cell *Board::lastSquare() {
	return firstSquare() + Common::boardSize * Common::boardSize;
}

void Board::initCell(Cell* c, uchar initMask, bool rowZero, bool rowN, bool colZero, bool colN) {
	c->wordFactor = (initMask >> 4);
	c->letterFactor = (initMask & 0x0F);
	c->rowZero = rowZero;
	c->rowN = rowN;
	c->colZero = colZero;
	c->colN = colN;
}

void Board::retractCell(Cell& c) {
	c.set(Common::EMPTYCHAR);
}

void Board::cementTile(Cell& cell) {
	cell.assertFilled();
}

void Board::reset() {
	bzero(squares, sizeof(Cell) * Common::boardSize * Common::boardSize);
	Cell *cptr = squares;
	for (const SquareType *tile = standardTilesStart(); tile < standardTilesEnd(); tile++)
		setCell(*cptr++, *tile);
	preservePriorsquares();
}

const string Board::formatBoard() {
	ostringstream str;
	Cell *cellPtr = firstSquare();
	Cell *endptr = lastSquare();
	str << "  | ABCDEFGHIJKLMNO\n";
	int row = 1;

	while (cellPtr < endptr) {
		str << setw(2) << setfill('0') << row++ << "| " << setw(1);
		Cell *eptr = cellPtr + Common::boardSize;
		while (cellPtr < eptr) {
			if (cellPtr->isValid())
				str << (char) (cellPtr->getLetter());
			else
				str << "_";
			cellPtr++;
		}
		str << '\n';
	}
	return str.str();
}

const string Board::toString() {
	string result;
	Cell *cellPtr = firstSquare();
	Cell *endptr = lastSquare();
	while (cellPtr < endptr) {
		Cell *eptr = cellPtr + Common::boardSize;
		while (cellPtr < eptr) {
			uchar letter = cellPtr->letter;
			result.push_back(letter == 0 ? '_' : letter);
			cellPtr++;
		}
		result.push_back('\n');
	}
	return result;
}

const string Board::getBoardString() {
	ostringstream str;
	Cell *cellPtr = firstSquare();
	Cell *endptr = lastSquare();
	while (cellPtr < endptr) {
		Cell *eptr = cellPtr + Common::boardSize;
		while (cellPtr < eptr) {
			str << cellPtr->toString() << '|';
			cellPtr++;
		}
		str << '\n';
	}
	return str.str();
}

string Board::describeCell(Cell *cell) const {
	ostringstream str;
	uchar ch = 'A' + col(cell);
	str << ch << (row(cell) + 1);
	return str.str();
}

string Board::describeCell(const Cell *cell) const {
	Cell * c = const_cast<Cell *> (cell);
	return describeCell(c);
}
